#include "buff.h"
#include <errno.h>
#include <string.h>
#include "io.h"

#define min(a, b) ((a) < (b) ? (a) : (b))

//    0 1 2 3 4     0 1 2 3 4     0 1 2 3 4
//   | | | | | |   | | | | | |   | | | | | |
//    o               o                   o
//    i               i                   i
//
//    0 1 2 3 4     0 1 2 3 4     0 1 2 3 4     0 1 2 3 4     0 1 2 3 4
//   |-|-|-| | |   | |-|-|-| |   | | |-|-|-|   |-| | |-|-|   |-|-| | |-|
//    o     i         o     i         i   o       i   o           i   o
//
//    0 1 2 3 4     0 1 2 3 4     0 1 2 3 4     0 1 2 3 4     0 1 2 3 4
//   |-|-| | | |   | |-|-| | |   | | |-|-| |   | | | |-|-|   |-| | | |-|
//    o   i           o   i           o   i     i     o         i     o

/**
 * errno:
 *     Except EINTR, EAGAIN, EWOULDBLOCK. see write(2).
 */
ssize_t buffRead(struct buff *buff, int fd) {
    size_t idx;
    ssize_t reqn, rspn, total = 0;

    if (buff->len >= EPOLL_BUF_SIZE)
        return 0; // is full

    if ((idx = (buff->off + buff->len) % EPOLL_BUF_SIZE) >= buff->off) {
        reqn = EPOLL_BUF_SIZE - idx;
        if ((rspn = readLowLevel(fd, buff->buf + idx, reqn)) < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
                rspn = 0;
            goto END;
        } else {
            total += rspn;
            if (rspn != reqn)
                goto END;
            idx = 0;
        }
    }

    if ((reqn = buff->off - idx) <= 0)
        goto END;
    if ((rspn = readLowLevel(fd, buff->buf + idx, reqn)) < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
            rspn = 0;
    } else {
        total += rspn;
    }

END:
    buff->len += total;
    return total ?: rspn;
}

/**
 * errno:
 *     Except EINTR, EAGAIN, EWOULDBLOCK. see write(2).
 */
ssize_t buffWrite(struct buff *buff, int fd) {
    ssize_t reqn, rspn, total = 0;

    if (buff->len == 0)
        return 0; // is empty

    if (buff->off + buff->len > EPOLL_BUF_SIZE) {
        reqn = EPOLL_BUF_SIZE - buff->off;
        if ((rspn = writeLowLevel(fd, buff->buf + buff->off, reqn)) < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
                rspn = 0;
            goto END;
        } else {
            total += rspn;
            buff->len -= rspn;
            if (rspn != reqn) {
                buff->off += rspn;
                goto END;
            } else {
                buff->off = 0;
            }
        }
    }

    reqn = buff->len;
    if ((rspn = writeLowLevel(fd, buff->buf + buff->off, reqn)) < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
            rspn = 0;
    } else {
        total += rspn;
        buff->len -= rspn;
        buff->off += rspn;
    }

END:
    return total ?: rspn;
}

size_t buffPut(struct buff *buff, const void *data, size_t count) {
    size_t idx;
    size_t n, total = 0;

    if (buff->len >= EPOLL_BUF_SIZE)
        return 0; // is full

    if ((idx = (buff->off + buff->len) % EPOLL_BUF_SIZE) >= buff->off) {
        n = min(EPOLL_BUF_SIZE - idx, count);
        memcpy(buff->buf + idx, data, n);
        total += n;
        if (n == count)
            goto END;
        idx = 0;
    }

    if ((n = buff->off - idx) > 0) {
        n = count - total;
        n = min(n, count);
        memcpy(buff->buf + idx, data + total, n);
        total += n;
    }

END:
    buff->len += total;
    return total;
}

size_t buffGet(struct buff *buff, void *data, size_t count) {
    size_t n, total = 0;

    if (buff->len == 0)
        return 0; // is empty

    if (buff->off + buff->len > EPOLL_BUF_SIZE) {
        n = min(EPOLL_BUF_SIZE - buff->off, count);
        memcpy(data, buff->buf + buff->off, n);
        total += n;
        buff->len -= n;
        buff->off = (buff->off + n) % EPOLL_BUF_SIZE;
        if (n == count)
            return total;
    }

    n = min(buff->len, count);
    memcpy(data + total, buff->buf + buff->off, n);
    total += n;
    buff->len -= n;
    buff->off += n;

    return total;
}

size_t buffPeek(struct buff *buff, void *data, size_t off, size_t count) {
    size_t n, total = 0;
    size_t roff, rlen;

    if (buff->len == 0 || off >= buff->len)
        return 0;

    roff = (buff->off + off) % EPOLL_BUF_SIZE;
    rlen = buff->len - off;

    if (roff + rlen > EPOLL_BUF_SIZE) {
        n = min(EPOLL_BUF_SIZE - roff, count);
        memcpy(data, buff->buf + roff, n);
        total += n;
        rlen -= n;
        roff = (roff + n) % EPOLL_BUF_SIZE;
        if (n == count)
            return total;
    }

    n = min(rlen, count);
    memcpy(data + total, buff->buf + roff, n);
    total += n;

    return total;
}

size_t buffDrop(struct buff *buff, size_t off, size_t count) {
    size_t total;

    if (off >= buff->len)
        return 0;

    total = min(count, buff->len);
    buff->len -= total;
    if (off == 0)
        buff->off = (buff->off + total) % EPOLL_BUF_SIZE;

    return total;
}

void buffReset(struct buff *buff) {
    buff->len = buff->off = 0;
}
